#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netdb.h>

#include "socket.h"

#define MAXHOSTNAME 15

Socket::Socket()
{
	_socket = -1;
}

Socket::~Socket()
{
	if(_socket >= 0) 
		close(_socket);
}

//serverul deschide un socket
int Socket::establish(unsigned short portnum) 
{ 
	char myname[MAXHOSTNAME+1]; 
	int s; 
	struct sockaddr_in sa; 
	struct hostent *hp; 

	memset(&sa, 0, sizeof(struct sockaddr_in)); /* clear our address */ 

	gethostname(myname, MAXHOSTNAME); /* who are we? */ 
	hp= gethostbyname(myname); /* get our address info */ 
	if (hp == NULL) /* we don't exist !? */ 
		return(-1); 

	sa.sin_family= hp->h_addrtype; /* this is our host address */ 
	sa.sin_port= htons(portnum); /* this is our port number */ 

	if ((s= socket(AF_INET, SOCK_STREAM, 0)) < 0) /* create socket */ 
		return(-1); 

	if (bind(s,(struct sockaddr *)&sa,sizeof(struct sockaddr_in)) < 0) 
	{ 
		close(s); 
		return(-1); /* bind address to socket */ 
	} 
	listen(s, 3); /* max # of queued connects */ 
	this->_socket = s;
	return(s); 
}

// asteapta conexiune pentru un socket deschis cu establish
// returneaza un socket conectat cu utilizatorul
int Socket::getConnection() 
{ 
	int t; /* socket of connection */ 
	if ((t = accept(this->_socket,NULL,NULL)) < 0) /* accept connection if there is one */ 
		return(-1); 
	return(t); 
}

// clientul apeleaza un socket deschis de server
// hostname poate fi adresa IP sau nume inregistrat
int Socket::callSocket(char *hostname, unsigned short portnum)
{
	struct sockaddr_in sa;
	struct hostent     *hp;

	// Stim adresa hostului?
	if ((hp = gethostbyname(hostname)) == NULL) {
		errno= ECONNREFUSED;
		return(-1);
	}

	memset(&sa,0,sizeof(sa));
	
	// Seteaza adresa hostului
	memcpy((char *)(&sa.sin_addr), hp->h_addr, hp->h_length);
	sa.sin_family= hp->h_addrtype;
	
	//Seteaza portul hostului
	sa.sin_port= htons((u_short)portnum);

	// Creeaza socket
	if ((this->_socket = socket(hp->h_addrtype,SOCK_STREAM,0)) < 0)
		return(-1);
	
	// Conecteaza
	if (connect(this->_socket,(struct sockaddr *)&sa,sizeof sa) < 0) {
		close(this->_socket);
		return(-1);
	}
	return (this->_socket);
}

//citeste stream de bytes, returneaza cati a citit
int Socket::readData(int s,     /* connected socket */
					 char *buf, /* pointer to the buffer */
					 int n      /* number of characters (bytes) we want */
					 )
{ 
	int bcount; /* counts bytes read */
	int br;     /* bytes read this pass */

	bcount= 0;
	br= 0;
	while (bcount < n) {             /* loop until full buffer */
		if ((br= read(s,buf,n-bcount)) > 0) {
			bcount += br;                /* increment byte counter */
			buf += br;                   /* move buffer ptr for next read */
		}
		else if (br < 0)               /* signal an error to the caller */
			return(-1);
	}
	return(bcount);
}

int Socket::writeData(int s,     /* connected socket */
					  char *buf, /* pointer to the buffer */
					  int n      /* number of characters (bytes) we want */
					  )
{ 
	int bcount; /* counts bytes written */
	int bw;     /* bytes written this pass */

	bcount= 0;
	bw= 0;
	while (bcount < n) {             /* loop until full buffer */
		if ((bw= write(s,buf,n-bcount)) > 0) {
			bcount += bw;                /* increment byte counter */
			buf += bw;                   /* move buffer ptr for next write */
		}
		else if (bw < 0)               /* signal an error to the caller */
			return(-1);
	}
	return(bcount);
}

void Socket::sendMessage(int socket, int messageType, char *message) {
	Socket::sendMessage(socket, messageType, message, false);
}

void Socket::sendMessage(int socket, int messageType, char *message, bool show) {
	char buffer[256];
	buffer[1] = (char)messageType;
	if(message) {
		if(strlen(message) > 253) {
			// mesajul este prea lung, va fi trunchiat
			strncpy(buffer+2, message, 253);
		} else {
			strcpy(buffer+2, message);
		}
		buffer[0] = strlen(buffer+2) + 2;
	} else {
		buffer[0] = 2;
	}
	Socket::writeData(socket, buffer, buffer[0]);
	if(show) {
		if(message) {
			printf("sendMessage la socketul[%d] - tipul %d - %s\n", socket, messageType, message);
		} else {
			printf("sendMessage la socketul[%d] - tipul %d\n", socket, messageType);
		}
	}
}

void Socket::getMessage(int socket, int &type, char *message) {
	Socket::getMessage(socket, type, message, false);
}

void Socket::getMessage(int socket, int &type, char *message, bool show) {
	char firstByte[1], buffer[256];
	int length;
	
	// citeste lungimea urmatorului mesaj
	Socket::readData(socket, firstByte, 1);
	length=(int)firstByte[0];
	
	// citeste urmatorul mesaj
	Socket::readData(socket, buffer, length-1);
	buffer[length-1] = '\0';
	
	// ia tipul mesajului
	type = (int)buffer[0];
	if(strlen(buffer)>1) {
		strcpy(message, buffer + 1);
	} else {
		message[0] = '\0';
	}
	
	if(show) {
		printf("getMessage la socketul[%d] - tipul %d - %s\n", socket, type, message);
	}
}
